其他
你有一份Rx编程秘籍请签收
作者:vivo互联网开发团队-Li Yuxiang
一、背景
二、Observable
Observable是一种概念,可以通过不同的方式去具体实现,本文通过高阶函数来实现两个常用Observable:fromEvent和Interval。通过讲解对Observable的订阅和取消订阅两个行为来帮助读者真正理解Observable是什么。
三、高阶函数
function foo(arg){
return function(){
console.log(arg)
}
}
const bar = foo(“hello world”)
bar() // hello world
ps:高阶函数能做的事情很多,这里仅仅针对本文需要的情形进行使用。
为啥要做这么一步封装呢?实际上这么做的效果就是“延迟”了调用。而一切的精髓就在这个“延迟”两个字里面。我们实际上是对一种行为进行了包装,看上去就像某种一致的东西,好比是快递盒子。
里面可以装不同的东西,但对于物流来说就是统一的东西。因此,就可以形成对快递盒的统一操作,比如堆叠、运输、存储、甚至是打开盒子这个动作也是一致的。
回到前面的例子,调用foo函数,相当于打包了一个快递盒,这个快递盒里面有一个固定的程序,就是当打开这个快递盒(调用bar)时执行一个打印操作。
我们可以有foo1、foo2、foo3……里面有各种各样的程序,但是这些foos,都有一个共同的操作就是“打开”。(前提是这个foo会返回一个函数,这样才能满足“打开”的操作,即调用返回的函数)。
function foo1(arg){
return function(){
console.log(arg+"?")
}
}
function foo2(arg){
return function(){
console.log(arg+"!")
}
}
const bar1 = foo1(“hello world”)
const bar2 = foo2("yes")
bar1()+bar2() // hello world? yes!
四、快递盒模型
btn.addEventListener("click",callback)
rx.fromEvent(btn,"click").subscribe(callback)
如果直接执行这个代码,确实效果是一样的。那么区别在哪儿呢?最直接的区别是,subscribe函数作用在fromEvent(……)上而不是btn上,而addEventListener是直接作用在btn上的。subscribe函数是某种“打开”操作,而fromEvent(……)则是某种快递盒。
fromEvent实际上是对addEventListener的“延迟”调用
function fromEvent(target,evtName){
return function(callback){
target.addEventListener(evtName,callback)
}
}
const ob = fromEvent(btn,"click")
ob(console.log)// 相当于 subscribe
哦!fromEvent本质上是高阶函数
至于如何实现subscribe来完成“打开”操作,不在本文讨论范围,在Rx编程中,这个subscribe的动作叫做“订阅”。“订阅”就是所有Observable的统一具备的操作。再次强调:本文中对Observable的“调用”在逻辑上相当于subscribe。
下面再举一个例子,基本可以让读者举二反N了。
function interval(period){
let i = 0
return function(callback){
setInterval(period,()=>callback(i++))
}
}
const ob = interval(1000)
ob(console.log)// 相当于 subscribe
从上面两个例子来看,无论是fromEvent(……)还是Interval(……),虽然内部是完全不同的逻辑,但是他们同属于“快递盒”这种东西,我们把它称之为Observable——可观察者。
fromEvent和Interval本身只是制作“快递盒”的模型,只有调用后返回的东西才是“快递盒”,即fromEvent(btn,"click")、interval(1000) 等等...
五、高阶快递盒
可以有很多种不同的可能性,比如可以逐个打开小的快递盒(concat),或者一次性打开所有小的快递盒(merge),也可以只打开那个最容易打开的快递盒(race)。
下面是一个简化版的merge方法:
function merge(...obs){
return function(callback){
obs.forEach(ob=>ob(callback)) // 打开所有快递盒
}
}
我们还是拿之前的fromEvent和interval来举例吧!
使用merge方法对两个Observable进行组合:
const ob1 = fromEvent(btn,'click') // 制作快递盒1
const ob2 = interval(1000) // 制作快递盒2
const ob = merge(ob1,ob2) //制作大快递盒
ob(console.log) // 打开大快递盒
当我们“打开”(订阅)这个大快递盒ob的时候,其中两个小快递盒也会被“打开”(订阅),任意一个小快递盒里面的逻辑都会被执行,我们就合并(merge)了两个Observable,变成了一个。
这就是我们为什么要辛辛苦苦把各种异步函数封装成快递盒(Observable)的原因了——方便对他们进行统一操作!当然仅仅只是“打开”(订阅)这个操作只是最初级的功能,下面开始进阶。
六、销毁快递盒
function fromEvent(target,evtName){
return function(callback){
target.addEventListener(evtName,callback)
}
}
那么我们怎么去销毁这个打开的快递盒呢?
首先我们需要得到一个已经打开的快递盒,上面的函数调用结果是void,我们无法做任何操作,所以我们需要构造出一个打开状态的快递盒。还是使用高阶函数的思想:在返回的函数里面再返回一个函数,用于销毁操作。
function fromEvent(target,evtName){
return function(callback){
target.addEventListener(evtName,callback)
return function(){
target.removeEventListener(evtName,callback)
}
}
}
const ob = fromEvent(btn,'click') // 制作快递盒
const sub = ob(console.log) // 打开快递盒,并得到一个可用于销毁的函数
sub() // 销毁快递盒
同理,对于interval,我们也可以如法炮制:
function interval(period){
let i = 0
return function(callback){
let id = setInterval(period,()=>callback(i++))
return function(){
clearInterval(id)
}
}
}
const ob = interval(1000) // 制作快递盒
const sub = ob(console.log) // 打开快递盒
sub() // 销毁快递盒
function merge(...obs){
return function(callback){
const subs = obs.map(ob=>ob(callback)) // 订阅所有并收集所有的销毁函数
return function(){
subs.forEach(sub=>sub()) // 遍历销毁函数并执行
}
}
}
const ob1 = fromEvent(btn,'click') // 制作快递盒1
const ob2 = interval(1000) // 制作快递盒2
const ob = merge(ob1,ob2) //制作大快递盒
const sub = ob(console.log) // 打开大快递盒
sub() // 销毁大快递盒
当我们销毁大快递盒的时候,就会把里面所有的小快递盒一起销毁。
六、补充
Observable除此以外,还有两个重要操作,即发出事件、完成/异常,(这两个操作属于是由Observable主动发起的回调,和操作的方向是相反的,所以其实不能称之为操作)。
这个两个行为用快递盒就不那么形象了,我们可以将Observable比做是水龙头,原先的打开快递盒变成拧开水龙头,而我们传入的回调函数就可以比喻成接水的水杯!由于大家对回调函数已经非常熟悉了,所以本文就不再赘述了。
七、后记
这些所谓的“延迟”执行就是Rx编程中幕后最难理解,也是最核心的部分。Rx的本质就是将异步函数封装起来,然后抽象成四大行为:订阅、取消订阅、发出事件、完成/异常。
实际实现Rx库的方法有很多,本文只是利用了高阶函数的思想来帮助大家理解Observable的本质,在官方实现的版本中,Observable这个快递盒并非是高阶函数,而是一个对象,但本质上是一样的,这里引出了一个话题:函数式编程与面向对象的异同,请听下回分解。
END
猜你喜欢